// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdint.h>

#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "media/base/media_switches.h"
#include "net/test/embedded_test_server/embedded_test_server.h"

using std::string;
namespace content {

struct SsrcEntry {
    string GetSsrcAttributeString() const
    {
        std::stringstream ss;
        ss << "a=ssrc:" << id;
        std::map<string, string>::const_iterator iter;
        for (iter = properties.begin(); iter != properties.end(); ++iter) {
            ss << " " << iter->first << ":" << iter->second;
        }
        return ss.str();
    }

    string GetAsJSON() const
    {
        std::stringstream ss;
        ss << "{";
        std::map<string, string>::const_iterator iter;
        for (iter = properties.begin(); iter != properties.end(); ++iter) {
            if (iter != properties.begin())
                ss << ",";
            ss << "\"" << iter->first << "\":\"" << iter->second << "\"";
        }
        ss << "}";
        return ss.str();
    }

    string id;
    std::map<string, string> properties;
};

struct EventEntry {
    string type;
    string value;
};

struct StatsUnit {
    string GetString() const
    {
        std::stringstream ss;
        ss << "{timestamp:" << timestamp << ", values:[";
        std::map<string, string>::const_iterator iter;
        for (iter = values.begin(); iter != values.end(); ++iter) {
            ss << "'" << iter->first << "','" << iter->second << "',";
        }
        ss << "]}";
        return ss.str();
    }

    int64_t timestamp;
    std::map<string, string> values;
};

struct StatsEntry {
    string type;
    string id;
    StatsUnit stats;
};

typedef std::map<string, std::vector<string>> StatsMap;

class PeerConnectionEntry {
public:
    PeerConnectionEntry(int pid, int lid)
        : pid_(pid)
        , lid_(lid)
    {
    }

    void AddEvent(const string& type, const string& value)
    {
        EventEntry entry = { type, value };
        events_.push_back(entry);
    }

    string getIdString() const
    {
        std::stringstream ss;
        ss << pid_ << "-" << lid_;
        return ss.str();
    }

    string getLogIdString() const
    {
        std::stringstream ss;
        ss << pid_ << "-" << lid_ << "-update-log";
        return ss.str();
    }

    string getAllUpdateString() const
    {
        std::stringstream ss;
        ss << "{pid:" << pid_ << ", lid:" << lid_ << ", log:[";
        for (size_t i = 0; i < events_.size(); ++i) {
            ss << "{type:'" << events_[i].type << "', value:'" << events_[i].value << "'},";
        }
        ss << "]}";
        return ss.str();
    }

    int pid_;
    int lid_;
    std::vector<EventEntry> events_;
    // This is a record of the history of stats value reported for each stats
    // report id (e.g. ssrc-1234) for each stats name (e.g. framerate).
    // It a 2-D map with each map entry is a vector of reported values.
    // It is used to verify the graph data series.
    std::map<string, StatsMap> stats_;
};

class UserMediaRequestEntry {
public:
    UserMediaRequestEntry(int pid,
        int rid,
        const std::string& origin,
        const std::string& audio_constraints,
        const std::string& video_constraints)
        : pid(pid)
        , rid(rid)
        , origin(origin)
        , audio_constraints(audio_constraints)
        , video_constraints(video_constraints)
    {
    }

    int pid;
    int rid;
    std::string origin;
    std::string audio_constraints;
    std::string video_constraints;
};

static const int64_t FAKE_TIME_STAMP = 3600000;

#if defined(OS_WIN)
// All tests are flaky on Windows: crbug.com/277322.
#define MAYBE_WebRtcInternalsBrowserTest DISABLED_WebRtcInternalsBrowserTest
#else
#define MAYBE_WebRtcInternalsBrowserTest WebRtcInternalsBrowserTest
#endif

class MAYBE_WebRtcInternalsBrowserTest : public ContentBrowserTest {
public:
    MAYBE_WebRtcInternalsBrowserTest() { }
    ~MAYBE_WebRtcInternalsBrowserTest() override { }

    void SetUpOnMainThread() override
    {
        base::CommandLine::ForCurrentProcess()->AppendSwitch(
            switches::kUseFakeUIForMediaStream);
        ASSERT_TRUE(base::CommandLine::ForCurrentProcess()->HasSwitch(
            switches::kUseFakeDeviceForMediaStream));
    }

protected:
    bool ExecuteJavascript(const string& javascript)
    {
        return ExecuteScript(shell(), javascript);
    }

    void ExpectTitle(const std::string& expected_title) const
    {
        base::string16 expected_title16(base::ASCIIToUTF16(expected_title));
        TitleWatcher title_watcher(shell()->web_contents(), expected_title16);
        EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
    }

    // Execute the javascript of addPeerConnection.
    void ExecuteAddPeerConnectionJs(const PeerConnectionEntry& pc)
    {
        std::stringstream ss;
        ss << "{pid:" << pc.pid_ << ", lid:" << pc.lid_ << ", "
           << "url:'u', rtcConfiguration:'s', constraints:'c'}";
        ASSERT_TRUE(ExecuteJavascript("addPeerConnection(" + ss.str() + ");"));
    }

    // Execute the javascript of removePeerConnection.
    void ExecuteRemovePeerConnectionJs(const PeerConnectionEntry& pc)
    {
        std::stringstream ss;
        ss << "{pid:" << pc.pid_ << ", lid:" << pc.lid_ << "}";

        ASSERT_TRUE(ExecuteJavascript("removePeerConnection(" + ss.str() + ");"));
    }

    // Execute the javascript of addGetUserMedia.
    void ExecuteAddGetUserMediaJs(const UserMediaRequestEntry& request)
    {
        std::stringstream ss;
        ss << "{pid:" << request.pid << ", rid:" << request.rid << ", origin:'"
           << request.origin << "', audio:'" << request.audio_constraints
           << "', video:'" << request.video_constraints << "'}";

        ASSERT_TRUE(ExecuteJavascript("addGetUserMedia(" + ss.str() + ");"));
    }

    // Execute the javascript of removeGetUserMediaForRenderer.
    void ExecuteRemoveGetUserMediaForRendererJs(int rid)
    {
        std::stringstream ss;
        ss << "{rid:" << rid << "}";
        ASSERT_TRUE(
            ExecuteJavascript("removeGetUserMediaForRenderer(" + ss.str() + ");"));
    }

    // Verifies that the DOM element with id |id| exists.
    void VerifyElementWithId(const string& id)
    {
        bool result = false;
        ASSERT_TRUE(ExecuteScriptAndExtractBool(
            shell(),
            "window.domAutomationController.send($('" + id + "') != null);",
            &result));
        EXPECT_TRUE(result);
    }

    // Verifies that the DOM element with id |id| does not exist.
    void VerifyNoElementWithId(const string& id)
    {
        bool result = false;
        ASSERT_TRUE(ExecuteScriptAndExtractBool(
            shell(),
            "window.domAutomationController.send($('" + id + "') == null);",
            &result));
        EXPECT_TRUE(result);
    }

    // Verifies the JS Array of userMediaRequests matches |requests|.
    void VerifyUserMediaRequest(
        const std::vector<UserMediaRequestEntry>& requests)
    {
        string json_requests;
        ASSERT_TRUE(
            ExecuteScriptAndExtractString(shell(),
                "window.domAutomationController.send("
                "    JSON.stringify(userMediaRequests));",
                &json_requests));
        std::unique_ptr<base::Value> value_requests = base::JSONReader::Read(json_requests);

        EXPECT_EQ(base::Value::Type::LIST, value_requests->GetType());

        base::ListValue* list_request = static_cast<base::ListValue*>(value_requests.get());
        EXPECT_EQ(requests.size(), list_request->GetSize());

        for (size_t i = 0; i < requests.size(); ++i) {
            base::DictionaryValue* dict = NULL;
            ASSERT_TRUE(list_request->GetDictionary(i, &dict));
            int pid, rid;
            std::string origin, audio, video;
            ASSERT_TRUE(dict->GetInteger("pid", &pid));
            ASSERT_TRUE(dict->GetInteger("rid", &rid));
            ASSERT_TRUE(dict->GetString("origin", &origin));
            ASSERT_TRUE(dict->GetString("audio", &audio));
            ASSERT_TRUE(dict->GetString("video", &video));
            EXPECT_EQ(requests[i].pid, pid);
            EXPECT_EQ(requests[i].rid, rid);
            EXPECT_EQ(requests[i].origin, origin);
            EXPECT_EQ(requests[i].audio_constraints, audio);
            EXPECT_EQ(requests[i].video_constraints, video);
        }

        bool user_media_tab_existed = false;
        ASSERT_TRUE(
            ExecuteScriptAndExtractBool(shell(),
                "window.domAutomationController.send("
                "    $('user-media-tab-id') != null);",
                &user_media_tab_existed));
        EXPECT_EQ(!requests.empty(), user_media_tab_existed);

        if (user_media_tab_existed) {
            int user_media_request_count = -1;
            ASSERT_TRUE(ExecuteScriptAndExtractInt(
                shell(),
                "window.domAutomationController.send("
                "    $('user-media-tab-id').childNodes.length);",
                &user_media_request_count));
            ASSERT_EQ(requests.size(), static_cast<size_t>(user_media_request_count));
        }
    }

    // Verifies that DOM for |pc| is correctly created with the right content.
    void VerifyPeerConnectionEntry(const PeerConnectionEntry& pc)
    {
        VerifyElementWithId(pc.getIdString());
        if (pc.events_.size() == 0)
            return;

        string log_id = pc.getLogIdString();
        VerifyElementWithId(log_id);
        string result;
        for (size_t i = 0; i < pc.events_.size(); ++i) {
            std::stringstream ss;
            ss << "var row = $('" << log_id << "').rows[" << (i + 1) << "];"
                                                                        "var cell = row.lastChild;"
                                                                        "window.domAutomationController.send(cell.firstChild.textContent);";
            ASSERT_TRUE(ExecuteScriptAndExtractString(shell(), ss.str(), &result));
            EXPECT_EQ(pc.events_[i].type + pc.events_[i].value, result);
        }
    }

    // Executes the javascript of updatePeerConnection and verifies the result.
    void ExecuteAndVerifyUpdatePeerConnection(
        PeerConnectionEntry& pc, const string& type, const string& value)
    {
        pc.AddEvent(type, value);

        std::stringstream ss;
        ss << "{pid:" << pc.pid_ << ", lid:" << pc.lid_ << ", type:'" << type << "', value:'" << value << "'}";
        ASSERT_TRUE(ExecuteJavascript("updatePeerConnection(" + ss.str() + ")"));

        VerifyPeerConnectionEntry(pc);
    }

    // Execute addStats and verifies that the stats table has the right content.
    void ExecuteAndVerifyAddStats(
        PeerConnectionEntry& pc, const string& type, const string& id,
        StatsUnit& stats)
    {
        StatsEntry entry = { type, id, stats };

        // Adds each new value to the map of stats history.
        std::map<string, string>::iterator iter;
        for (iter = stats.values.begin(); iter != stats.values.end(); iter++) {
            pc.stats_[id][iter->first].push_back(iter->second);
        }
        std::stringstream ss;
        ss << "{pid:" << pc.pid_ << ", lid:" << pc.lid_ << ","
                                                           "reports:["
           << "{id:'" << id << "', type:'" << type << "', "
                                                      "stats:"
           << stats.GetString() << "}]}";

        ASSERT_TRUE(ExecuteJavascript("addStats(" + ss.str() + ")"));
        VerifyStatsTable(pc, entry);
    }

    // Verifies that the stats table has the right content.
    void VerifyStatsTable(const PeerConnectionEntry& pc,
        const StatsEntry& report)
    {
        string table_id = pc.getIdString() + "-table-" + report.id;
        VerifyElementWithId(table_id);

        std::map<string, string>::const_iterator iter;
        for (iter = report.stats.values.begin();
             iter != report.stats.values.end(); iter++) {
            VerifyStatsTableRow(table_id, iter->first, iter->second);
        }
    }

    // Verifies that the row named as |name| of the stats table |table_id| has
    // the correct content as |name| : |value|.
    void VerifyStatsTableRow(const string& table_id,
        const string& name,
        const string& value)
    {
        VerifyElementWithId(table_id + "-" + name);

        string result;
        ASSERT_TRUE(ExecuteScriptAndExtractString(
            shell(),
            "var row = $('" + table_id + "-" + name + "');"
                                                      "var name = row.cells[0].textContent;"
                                                      "var value = row.cells[1].textContent;"
                                                      "window.domAutomationController.send(name + ':' + value)",
            &result));
        EXPECT_EQ(name + ":" + value, result);
    }

    // Verifies that the graph data series consistent with pc.stats_.
    void VerifyStatsGraph(const PeerConnectionEntry& pc)
    {
        std::map<string, StatsMap>::const_iterator stream_iter;
        for (stream_iter = pc.stats_.begin();
             stream_iter != pc.stats_.end(); stream_iter++) {
            StatsMap::const_iterator stats_iter;
            for (stats_iter = stream_iter->second.begin();
                 stats_iter != stream_iter->second.end();
                 stats_iter++) {
                string graph_id = stream_iter->first + "-" + stats_iter->first;
                for (size_t i = 0; i < stats_iter->second.size(); ++i) {
                    float number;
                    std::stringstream stream(stats_iter->second[i]);
                    stream >> number;
                    if (stream.fail())
                        continue;
                    VerifyGraphDataPoint(
                        pc.getIdString(), graph_id, i, stats_iter->second[i]);
                }
            }
        }
    }

    // Verifies that the graph data point at index |index| has value |value|.
    void VerifyGraphDataPoint(const string& pc_id, const string& graph_id,
        int index, const string& value)
    {
        bool result = false;
        ASSERT_TRUE(ExecuteScriptAndExtractBool(
            shell(),
            "window.domAutomationController.send("
            "graphViews['"
                + pc_id + "-" + graph_id + "'] != null)",
            &result));
        EXPECT_TRUE(result);

        std::stringstream ss;
        ss << "var dp = peerConnectionDataStore['" << pc_id << "']"
                                                               ".getDataSeries('"
           << graph_id << "').dataPoints_[" << index << "];"
                                                        "window.domAutomationController.send(dp.value.toString())";
        string actual_value;
        ASSERT_TRUE(
            ExecuteScriptAndExtractString(shell(), ss.str(), &actual_value));
        EXPECT_EQ(value, actual_value);
    }

    // Get the JSON string of the ssrc info from the page.
    string GetSsrcInfo(const string& ssrc_id)
    {
        string result;
        EXPECT_TRUE(ExecuteScriptAndExtractString(
            shell(),
            "window.domAutomationController.send(JSON.stringify("
            "ssrcInfoManager.streamInfoContainer_['"
                + ssrc_id + "']))",
            &result));
        return result;
    }

    int GetSsrcInfoBlockCount(Shell* shell)
    {
        int count = 0;
        EXPECT_TRUE(ExecuteScriptAndExtractInt(
            shell,
            "window.domAutomationController.send("
            "    document.getElementsByClassName("
            "        ssrcInfoManager.SSRC_INFO_BLOCK_CLASS).length);",
            &count));
        return count;
    }

    // Verifies |dump| contains |peer_connection_number| peer connection dumps,
    // each containing |update_number| updates and |stats_number| stats tables.
    void VerifyPageDumpStructure(base::Value* dump,
        int peer_connection_number,
        int update_number,
        int stats_number)
    {
        EXPECT_NE((base::Value*)NULL, dump);
        EXPECT_EQ(base::Value::Type::DICTIONARY, dump->GetType());

        base::DictionaryValue* dict_dump = static_cast<base::DictionaryValue*>(dump);
        EXPECT_EQ((size_t)peer_connection_number, dict_dump->size());

        base::DictionaryValue::Iterator it(*dict_dump);
        for (; !it.IsAtEnd(); it.Advance()) {
            base::Value* value = NULL;
            dict_dump->Get(it.key(), &value);
            EXPECT_EQ(base::Value::Type::DICTIONARY, value->GetType());
            base::DictionaryValue* pc_dump = static_cast<base::DictionaryValue*>(value);
            EXPECT_TRUE(pc_dump->HasKey("updateLog"));
            EXPECT_TRUE(pc_dump->HasKey("stats"));

            // Verifies the number of updates.
            pc_dump->Get("updateLog", &value);
            EXPECT_EQ(base::Value::Type::LIST, value->GetType());
            base::ListValue* list = static_cast<base::ListValue*>(value);
            EXPECT_EQ((size_t)update_number, list->GetSize());

            // Verifies the number of stats tables.
            pc_dump->Get("stats", &value);
            EXPECT_EQ(base::Value::Type::DICTIONARY, value->GetType());
            base::DictionaryValue* dict = static_cast<base::DictionaryValue*>(value);
            EXPECT_EQ((size_t)stats_number, dict->size());
        }
    }

    // Verifies |dump| contains the correct statsTable and statsDataSeries for
    // |pc|.
    void VerifyStatsDump(base::Value* dump,
        const PeerConnectionEntry& pc,
        const string& report_type,
        const string& report_id,
        const StatsUnit& stats)
    {
        EXPECT_NE((base::Value*)NULL, dump);
        EXPECT_EQ(base::Value::Type::DICTIONARY, dump->GetType());

        base::DictionaryValue* dict_dump = static_cast<base::DictionaryValue*>(dump);
        base::Value* value = NULL;
        dict_dump->Get(pc.getIdString(), &value);
        base::DictionaryValue* pc_dump = static_cast<base::DictionaryValue*>(value);

        // Verifies there is one data series per stats name.
        value = NULL;
        pc_dump->Get("stats", &value);
        EXPECT_EQ(base::Value::Type::DICTIONARY, value->GetType());

        base::DictionaryValue* dataSeries = static_cast<base::DictionaryValue*>(value);
        EXPECT_EQ(stats.values.size(), dataSeries->size());
    }
};

IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest,
    AddAndRemovePeerConnection)
{
    GURL url("chrome://webrtc-internals");
    NavigateToURL(shell(), url);

    // Add two PeerConnections and then remove them.
    PeerConnectionEntry pc_1(1, 0);
    ExecuteAddPeerConnectionJs(pc_1);
    VerifyPeerConnectionEntry(pc_1);

    PeerConnectionEntry pc_2(2, 1);
    ExecuteAddPeerConnectionJs(pc_2);
    VerifyPeerConnectionEntry(pc_2);

    ExecuteRemovePeerConnectionJs(pc_1);
    VerifyNoElementWithId(pc_1.getIdString());
    VerifyPeerConnectionEntry(pc_2);

    ExecuteRemovePeerConnectionJs(pc_2);
    VerifyNoElementWithId(pc_2.getIdString());
}

IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest,
    UpdateAllPeerConnections)
{
    GURL url("chrome://webrtc-internals");
    NavigateToURL(shell(), url);

    PeerConnectionEntry pc_0(1, 0);
    pc_0.AddEvent("e1", "v1");
    pc_0.AddEvent("e2", "v2");
    PeerConnectionEntry pc_1(1, 1);
    pc_1.AddEvent("e3", "v3");
    pc_1.AddEvent("e4", "v4");
    string pc_array = "[" + pc_0.getAllUpdateString() + ", " + pc_1.getAllUpdateString() + "]";
    EXPECT_TRUE(ExecuteJavascript("updateAllPeerConnections(" + pc_array + ");"));
    VerifyPeerConnectionEntry(pc_0);
    VerifyPeerConnectionEntry(pc_1);
}

IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest, UpdatePeerConnection)
{
    GURL url("chrome://webrtc-internals");
    NavigateToURL(shell(), url);

    // Add one PeerConnection and send one update.
    PeerConnectionEntry pc_1(1, 0);
    ExecuteAddPeerConnectionJs(pc_1);

    ExecuteAndVerifyUpdatePeerConnection(pc_1, "e1", "v1");

    // Add another PeerConnection and send two updates.
    PeerConnectionEntry pc_2(1, 1);
    ExecuteAddPeerConnectionJs(pc_2);

    SsrcEntry ssrc1, ssrc2;
    ssrc1.id = "ssrcid1";
    ssrc1.properties["msid"] = "mymsid";
    ssrc2.id = "ssrcid2";
    ssrc2.properties["label"] = "mylabel";
    ssrc2.properties["cname"] = "mycname";

    ExecuteAndVerifyUpdatePeerConnection(pc_2, "setRemoteDescription",
        ssrc1.GetSsrcAttributeString());

    ExecuteAndVerifyUpdatePeerConnection(pc_2, "setLocalDescription",
        ssrc2.GetSsrcAttributeString());

    EXPECT_EQ(ssrc1.GetAsJSON(), GetSsrcInfo(ssrc1.id));
    EXPECT_EQ(ssrc2.GetAsJSON(), GetSsrcInfo(ssrc2.id));

    StatsUnit stats = { FAKE_TIME_STAMP };
    stats.values["ssrc"] = ssrc1.id;
    ExecuteAndVerifyAddStats(pc_2, "ssrc", "dummyId", stats);
    EXPECT_GT(GetSsrcInfoBlockCount(shell()), 0);
}

// Tests that adding random named stats updates the dataSeries and graphs.
IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest, AddStats)
{
    GURL url("chrome://webrtc-internals");
    NavigateToURL(shell(), url);

    PeerConnectionEntry pc(1, 0);
    ExecuteAddPeerConnectionJs(pc);

    const string type = "ssrc";
    const string id = "ssrc-1234";
    StatsUnit stats = { FAKE_TIME_STAMP };
    stats.values["trackId"] = "abcd";
    stats.values["bitrate"] = "2000";
    stats.values["framerate"] = "30";

    // Add new stats and verify the stats table and graphs.
    ExecuteAndVerifyAddStats(pc, type, id, stats);
    VerifyStatsGraph(pc);

    // Update existing stats and verify the stats table and graphs.
    stats.values["bitrate"] = "2001";
    stats.values["framerate"] = "31";
    ExecuteAndVerifyAddStats(pc, type, id, stats);
    VerifyStatsGraph(pc);
}

// Tests that the bandwidth estimation values are drawn on a single graph.
IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest, BweCompoundGraph)
{
    GURL url("chrome://webrtc-internals");
    NavigateToURL(shell(), url);

    PeerConnectionEntry pc(1, 0);
    ExecuteAddPeerConnectionJs(pc);

    StatsUnit stats = { FAKE_TIME_STAMP };
    stats.values["googAvailableSendBandwidth"] = "1000000";
    stats.values["googTargetEncBitrate"] = "1000";
    stats.values["googActualEncBitrate"] = "1000000";
    stats.values["googRetransmitBitrate"] = "10";
    stats.values["googTransmitBitrate"] = "1000000";
    const string stats_type = "bwe";
    const string stats_id = "videobwe";
    ExecuteAndVerifyAddStats(pc, stats_type, stats_id, stats);

    string graph_id = pc.getIdString() + "-" + stats_id + "-bweCompound";
    bool result = false;
    // Verify that the bweCompound graph exists.
    ASSERT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send("
        "   graphViews['"
            + graph_id + "'] != null)",
        &result));
    EXPECT_TRUE(result);

    // Verify that the bweCompound graph contains multiple dataSeries.
    int count = 0;
    ASSERT_TRUE(ExecuteScriptAndExtractInt(
        shell(),
        "window.domAutomationController.send("
        "   graphViews['"
            + graph_id + "'].getDataSeriesCount())",
        &count));
    EXPECT_EQ((int)stats.values.size(), count);
}

// Tests that the total packet/byte count is converted to count per second,
// and the converted data is drawn.
IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest, ConvertedGraphs)
{
    GURL url("chrome://webrtc-internals");
    NavigateToURL(shell(), url);

    PeerConnectionEntry pc(1, 0);
    ExecuteAddPeerConnectionJs(pc);

    const string stats_type = "s";
    const string stats_id = "1";
    const int num_converted_stats = 4;
    const string stats_names[] = { "packetsSent", "bytesSent", "packetsReceived", "bytesReceived" };
    const string converted_names[] = { "packetsSentPerSecond", "bitsSentPerSecond",
        "packetsReceivedPerSecond", "bitsReceivedPerSecond" };
    const string first_value = "1000";
    const string second_value = "2000";
    const string converted_values[] = { "1000", "8000", "1000", "8000" };

    // Send the first data point.
    StatsUnit stats = { FAKE_TIME_STAMP };
    for (int i = 0; i < num_converted_stats; ++i)
        stats.values[stats_names[i]] = first_value;

    ExecuteAndVerifyAddStats(pc, stats_type, stats_id, stats);

    // Send the second data point at 1000ms after the first data point.
    stats.timestamp += 1000;
    for (int i = 0; i < num_converted_stats; ++i)
        stats.values[stats_names[i]] = second_value;
    ExecuteAndVerifyAddStats(pc, stats_type, stats_id, stats);

    // Verifies the graph data matches converted_values.
    for (int i = 0; i < num_converted_stats; ++i) {
        VerifyGraphDataPoint(pc.getIdString(), stats_id + "-" + converted_names[i],
            1, converted_values[i]);
    }
}

// Timing out on ARM linux bot: http://crbug.com/238490
// Disabling due to failure on Linux, Mac, Win: http://crbug.com/272413
// Sanity check of the page content under a real PeerConnection call.
IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest,
    DISABLED_WithRealPeerConnectionCall)
{
    // Start a peerconnection call in the first window.
    ASSERT_TRUE(embedded_test_server()->Start());
    GURL url(embedded_test_server()->GetURL("/media/peerconnection-call.html"));
    NavigateToURL(shell(), url);
    ASSERT_TRUE(ExecuteJavascript("call({video:true});"));
    ExpectTitle("OK");

    // Open webrtc-internals in the second window.
    GURL url2("chrome://webrtc-internals");
    Shell* shell2 = CreateBrowser();
    NavigateToURL(shell2, url2);

    const int NUMBER_OF_PEER_CONNECTIONS = 2;

    // Verifies the number of peerconnections.
    int count = 0;
    ASSERT_TRUE(ExecuteScriptAndExtractInt(
        shell2,
        "window.domAutomationController.send("
        "    $('peer-connections-list').getElementsByTagName('li').length);",
        &count));
    EXPECT_EQ(NUMBER_OF_PEER_CONNECTIONS, count);

    // Verifies the the event tables.
    ASSERT_TRUE(ExecuteScriptAndExtractInt(
        shell2,
        "window.domAutomationController.send($('peer-connections-list')"
        "    .getElementsByClassName('update-log-table').length);",
        &count));
    EXPECT_EQ(NUMBER_OF_PEER_CONNECTIONS, count);

    ASSERT_TRUE(ExecuteScriptAndExtractInt(
        shell2,
        "window.domAutomationController.send($('peer-connections-list')"
        "    .getElementsByClassName('update-log-table')[0].rows.length);",
        &count));
    EXPECT_GT(count, 1);

    ASSERT_TRUE(ExecuteScriptAndExtractInt(
        shell2,
        "window.domAutomationController.send($('peer-connections-list')"
        "    .getElementsByClassName('update-log-table')[1].rows.length);",
        &count));
    EXPECT_GT(count, 1);

    // Wait until the stats table containers are created.
    count = 0;
    while (count != NUMBER_OF_PEER_CONNECTIONS) {
        ASSERT_TRUE(ExecuteScriptAndExtractInt(
            shell2,
            "window.domAutomationController.send("
            "    $('peer-connections-list').getElementsByClassName("
            "        'stats-table-container').length);",
            &count));
    }

    // Verifies each stats table having more than one rows.
    bool result = false;
    ASSERT_TRUE(ExecuteScriptAndExtractBool(
        shell2,
        "var tableContainers = $('peer-connections-list')"
        "    .getElementsByClassName('stats-table-container');"
        "var result = true;"
        "for (var i = 0; i < tableContainers.length && result; ++i) {"
        "  var tables = tableContainers[i].getElementsByTagName('table');"
        "  for (var j = 0; j < tables.length && result; ++j) {"
        "    result = (tables[j].rows.length > 1);"
        "  }"
        "  if (!result) {"
        "    console.log(tableContainers[i].innerHTML);"
        "  }"
        "}"
        "window.domAutomationController.send(result);",
        &result));

    EXPECT_TRUE(result);

    count = GetSsrcInfoBlockCount(shell2);
    EXPECT_GT(count, 0);
}

IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest, CreatePageDump)
{
    GURL url("chrome://webrtc-internals");
    NavigateToURL(shell(), url);

    PeerConnectionEntry pc_0(1, 0);
    pc_0.AddEvent("e1", "v1");
    pc_0.AddEvent("e2", "v2");
    PeerConnectionEntry pc_1(1, 1);
    pc_1.AddEvent("e3", "v3");
    pc_1.AddEvent("e4", "v4");
    string pc_array = "[" + pc_0.getAllUpdateString() + ", " + pc_1.getAllUpdateString() + "]";
    EXPECT_TRUE(ExecuteJavascript("updateAllPeerConnections(" + pc_array + ");"));

    // Verifies the peer connection data store can be created without stats.
    string dump_json;
    ASSERT_TRUE(ExecuteScriptAndExtractString(
        shell(),
        "window.domAutomationController.send("
        "    JSON.stringify(peerConnectionDataStore));",
        &dump_json));
    std::unique_ptr<base::Value> dump = base::JSONReader::Read(dump_json);
    VerifyPageDumpStructure(dump.get(),
        2 /*peer_connection_number*/,
        2 /*update_number*/,
        0 /*stats_number*/);

    // Adds a stats report.
    const string type = "dummy";
    const string id = "1234";
    StatsUnit stats = { FAKE_TIME_STAMP };
    stats.values["bitrate"] = "2000";
    stats.values["framerate"] = "30";
    ExecuteAndVerifyAddStats(pc_0, type, id, stats);

    ASSERT_TRUE(ExecuteScriptAndExtractString(
        shell(),
        "window.domAutomationController.send("
        "    JSON.stringify(peerConnectionDataStore));",
        &dump_json));
    dump = base::JSONReader::Read(dump_json);
    VerifyStatsDump(dump.get(), pc_0, type, id, stats);
}

IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest, UpdateGetUserMedia)
{
    GURL url("chrome://webrtc-internals");
    NavigateToURL(shell(), url);

    UserMediaRequestEntry request1(1, 1, "origin", "ac", "vc");
    UserMediaRequestEntry request2(2, 2, "origin2", "ac2", "vc2");
    ExecuteAddGetUserMediaJs(request1);
    ExecuteAddGetUserMediaJs(request2);

    std::vector<UserMediaRequestEntry> list;
    list.push_back(request1);
    list.push_back(request2);
    VerifyUserMediaRequest(list);

    ExecuteRemoveGetUserMediaForRendererJs(1);
    list.erase(list.begin());
    VerifyUserMediaRequest(list);

    ExecuteRemoveGetUserMediaForRendererJs(2);
    list.erase(list.begin());
    VerifyUserMediaRequest(list);
}

// Tests that the received propagation delta values are converted and drawn
// correctly.
IN_PROC_BROWSER_TEST_F(MAYBE_WebRtcInternalsBrowserTest,
    ReceivedPropagationDelta)
{
    GURL url("chrome://webrtc-internals");
    NavigateToURL(shell(), url);

    PeerConnectionEntry pc(1, 0);
    ExecuteAddPeerConnectionJs(pc);

    StatsUnit stats = { FAKE_TIME_STAMP };
    stats.values["googReceivedPacketGroupArrivalTimeDebug"] = "[1000, 1100, 1200]";
    stats.values["googReceivedPacketGroupPropagationDeltaDebug"] = "[10, 20, 30]";
    const string stats_type = "bwe";
    const string stats_id = "videobwe";
    ExecuteAndVerifyAddStats(pc, stats_type, stats_id, stats);

    string graph_id = pc.getIdString() + "-" + stats_id + "-googReceivedPacketGroupPropagationDeltaDebug";
    string data_series_id = stats_id + "-googReceivedPacketGroupPropagationDeltaDebug";
    bool result = false;
    // Verify that the graph exists.
    ASSERT_TRUE(ExecuteScriptAndExtractBool(
        shell(),
        "window.domAutomationController.send("
        "   graphViews['"
            + graph_id + "'] != null)",
        &result));
    EXPECT_TRUE(result);

    // Verify that the graph contains multiple data points.
    int count = 0;
    ASSERT_TRUE(ExecuteScriptAndExtractInt(
        shell(),
        "window.domAutomationController.send("
        "   graphViews['"
            + graph_id + "'].getDataSeriesCount())",
        &count));
    EXPECT_EQ(1, count);
    ASSERT_TRUE(ExecuteScriptAndExtractInt(
        shell(),
        "window.domAutomationController.send("
        "   peerConnectionDataStore['"
            + pc.getIdString() + "']" + "       .getDataSeries('" + data_series_id + "').getCount())",
        &count));
    EXPECT_EQ(3, count);
}

} // namespace content
